//
//  OPLL.hpp
//  Clock Signal
//
//  Created by Thomas Harte on 03/05/2020.
//  Copyright © 2020 Thomas Harte. All rights reserved.
//

#pragma once

#include "Implementation/OPLBase.hpp"
#include "Implementation/EnvelopeGenerator.hpp"
#include "Implementation/KeyLevelScaler.hpp"
#include "Implementation/PhaseGenerator.hpp"
#include "Implementation/LowFrequencyOscillator.hpp"
#include "Implementation/WaveformGenerator.hpp"

#include <atomic>

namespace Yamaha::OPL {

class OPLL: public OPLBase<OPLL, false> {
public:
	/// Creates a new OPLL or VRC7.
	OPLL(Concurrency::AsyncTaskQueue<false> &task_queue, int audio_divider = 1, bool is_vrc7 = false);

	/// As per ::SampleSource; provides audio output.
	template <Outputs::Speaker::Action action>
	void apply_samples(std::size_t number_of_samples, Outputs::Speaker::MonoSample *);
	void set_sample_volume_range(std::int16_t range);
	bool is_zero_level() const { return false; }	// TODO.

	// The OPLL is generally 'half' as loud as it's told to be. This won't strictly be true in
	// rhythm mode, but it's correct for melodic output.
	double average_output_peak() const { return 0.5; }

	/// Reads from the OPL.
	uint8_t read(uint16_t address);

private:
	friend OPLBase<OPLL, false>;
	void write_register(uint8_t address, uint8_t value);

	int audio_divider_ = 0;
	int audio_offset_ = 0;
	std::atomic<int> total_volume_;

	int16_t output_levels_[18];
	void update_all_channels();

	int melodic_output(int channel);
	int bass_drum() const;
	int tom_tom() const;
	int snare_drum() const;
	int cymbal() const;
	int high_hat() const;

	static constexpr int period_precision = 9;
	static constexpr int envelope_precision = 7;

	// Standard melodic phase and envelope generators;
	//
	// These are assigned as:
	//
	//		[x], 0 <= x < 9		= carrier for channel x;
	//		[x+9]				= modulator for channel x.
	//
	PhaseGenerator<period_precision> phase_generators_[18];
	EnvelopeGenerator<envelope_precision, period_precision> envelope_generators_[18];
	KeyLevelScaler<period_precision> key_level_scalers_[18];

	// Dedicated rhythm envelope generators and attenuations.
	EnvelopeGenerator<envelope_precision, period_precision> rhythm_envelope_generators_[6];
	enum RhythmIndices {
		HighHat = 0,
		Cymbal = 1,
		TomTom = 2,
		Snare = 3,
		BassCarrier = 4,
		BassModulator = 5
	};

	// Channel specifications.
	struct Channel {
		int octave = 0;
		int period = 0;
		int instrument = 0;

		int attenuation = 0;
		int modulator_attenuation = 0;

		Waveform carrier_waveform = Waveform::Sine;
		Waveform modulator_waveform = Waveform::Sine;

		int carrier_key_rate_scale_multiplier = 0;
		int modulator_key_rate_scale_multiplier = 0;

		LogSign modulator_output;
		int modulator_feedback = 0;

		bool use_sustain = false;
	} channels_[9];

	// The low-frequency oscillator.
	LowFrequencyOscillator oscillator_;
	bool rhythm_mode_enabled_ = false;
	bool is_vrc7_ = false;

	// Contains the current configuration of the custom instrument.
	uint8_t custom_instrument_[8] = {0, 0, 0, 0, 0, 0, 0, 0};

	// Helpers to push per-channel information.

	/// Pushes the current octave and period to channel @c channel.
	void set_channel_period(int channel);

	/// Installs the appropriate instrument on channel @c channel.
	void install_instrument(int channel);

	/// Sets whether the sustain level is used for channel @c channel based on its current instrument
	/// and the user's selection.
	void set_use_sustain(int channel);

	/// @returns The 8-byte definition of instrument @c instrument.
	const uint8_t *instrument_definition(int instrument, int channel) const;
};

}
